package tinyinventor.compiler;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import codeblocks.Block;

public class PythonCompiler extends Compiler {

	public static final String FILE_NAME = "tinyinventor";
	
	// Prototype components loaded from XML file
	private static Map<String,PythonComponent> prototypeComponents = new HashMap<String, PythonComponent>();
	
	protected Map<Long, PythonComponent> blockComponents;
	
	public PythonCompiler() {
		blockComponents = new HashMap<Long, PythonComponent>();
	}
	
	@Override
	protected Component getComponent(Block block) throws CompileException {
		if(blockComponents.containsKey(block.getBlockID())) {
			return blockComponents.get(block.getBlockID());
		} else {
			PythonComponent c = getPrototypeComponent(block.getGenusName()).clone();
			blockComponents.put(block.getBlockID(), c);
			return c;
		}
	}

	/**
	 * Write the current compiled program to files under path
	 * @param path the path were the files are to be written too
	 * @throws IOException
	 */
	@Override
	public void build(String path) throws IOException {
		super.build(path);
		
		File pythonFile = new File(path + "/" + FILE_NAME + ".py");
		StringBuilder code = new StringBuilder();
		
		code.append("from tos import *\n");
		code.append("from synctos import *\n");
		code.append("from threading import *\n");
		code.append("import time\n");
		code.append("import Tkinter\n");
		code.append("import gui\n");
		code.append("import socket\n");
		code.append("import struct\n");
		code.append("import pylab\n");
		
		code.append("SUCCESS = 0\n");
		code.append("FAIL = 1\n\n");
		
		code.append("screen = gui.ScreenSpace()\n");
		code.append(getDeclarationCode());
		
		code.append("def main(): #{\n");
		code.append(getMainCode());
		code.append("screen.mainloop()\n");
		code.append("#}\n\n");
		
		code.append("if __name__ == \"__main__\": #{\n");
		code.append("main() \n#}\n");
		
		BufferedWriter writer = new BufferedWriter(new FileWriter(pythonFile));
		writer.write(code.toString());
		writer.close();
		indent(pythonFile);
	}
	
	/**
	 * Get the compilers current components python declaration code
	 * @return the code
	 */
	private String getDeclarationCode() {
		StringBuilder result = new StringBuilder();
		String code;
		for(PythonComponent c : blockComponents.values()) {
			code = c.getDeclarationCode();
			if(!code.equals("")) {
				result.append(code);
				result.append("\n\n");
			}
		}
		return result.toString();
	}
	
	/**
	 * Get the compilers current components python declaration code
	 * @return the code
	 */
	private String getMainCode() {
		StringBuilder result = new StringBuilder();
		String code;
		for(PythonComponent c : blockComponents.values()) {
			code = c.getMainCode();
			if(!code.equals("")) {
				result.append(code);
				result.append("\n");
			}
		}
		return result.toString();
	}
	
	/**
	 * Gets a python prototype component
	 * @param name the name of the component
	 * @return the component
	 * @throws CompileException if the prototype component cannot be found
	 */
	private static PythonComponent getPrototypeComponent(String name) throws CompileException {
		if(prototypeComponents.containsKey(name)) {
			return prototypeComponents.get(name);
		} else {
			throw new CompileException("Block "+name+" is not a valid Python block.");
		}
	}

	
	/**
	 * Gets python prototype components from a components declaration file
	 * @param path the path to the components declaration
	 */
	public static void loadComponentsFromPath(String path) {
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		DocumentBuilder builder;
		Document doc;
		Element element;
		try {
			builder = factory.newDocumentBuilder();
			doc = builder.parse(new File(path));
			element = doc.getDocumentElement();
			
			NodeList componentsRoot = element.getElementsByTagName("Component");
			if(componentsRoot==null) {
				System.err.println("No components found in " + path);
			} else {
				
				for(int i=0; i<componentsRoot.getLength(); i++) {
					Node component = componentsRoot.item(i);
					
					//System.out.println(component);
					
					if(!component.getNodeName().equals("Component")) {
						System.err.println("Not a component");
						continue;
					}
				
					NamedNodeMap attr = component.getAttributes();
					if(attr==null) continue;
					Node nameNode = attr.getNamedItem("name");
					if(nameNode==null) continue;
					String name = nameNode.getTextContent();
					
					String rawCode = "";
					String rawDeclarationCode = "";
					String rawMainCode = "";
					
					if(component.hasChildNodes()) {
						Node child = component.getFirstChild();
						while(child!=null) {
							if(child.getNodeName().equals("Code")) {
								rawCode = child.getTextContent().trim();
							} else if(child.getNodeName().equals("DeclarationCode")) {
								rawDeclarationCode = child.getTextContent().trim();
							} else if(child.getNodeName().equals("MainCode")) {
								rawMainCode = child.getTextContent().trim();
							}
							
							
							child = child.getNextSibling();
						}
					}
					
					PythonComponent prototypeComponent = new PythonComponent(name, rawCode, rawDeclarationCode, rawMainCode);
					prototypeComponents.put(name, prototypeComponent);
					
					System.out.println(prototypeComponent);
				}
			}
			
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		} catch (SAXException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		//System.out.println("prototypeComponents: " + prototypeComponents);
		
	}
	
	
	
}
